// 自定义工具函数
import Vue from 'vue'
import router from '@/router'
import $store from '@/store'
import { regExp } from './regExp'
import Big from 'big.js'
import JsPDF from 'jspdf'
import { Message } from 'element-ui'

/**
 * 系统设置
 */
export const systemSettings = Object.freeze(
    JSON.parse(localStorage.getItem('$systemSettings') || 'null') ||
    {
        systemSettings: true,
        openNewPage: true,
    }
)

Vue.mixin({
    created() {
        routeEach.run(this)
    },
    activated() {
        routeEach.run(this)
    }
})

/**
 * async/await捕获错误
 * @param { any | Promise<any> } pm 目标Promise或值
 * @return { Promise<
 *   { value: any, isResolve: true, isReject: false } |
 *   { value: any, isResolve: false, isReject: true }
 * > } 返回能捕获错误的Promise
 */
export const asyncTo = pm => {
    if (typeof pm?.then !== 'function') {
        pm = Promise.resolve(pm)
    }
    return pm.then(
        res => ({
            value: res,
            isResolve: true,
            isReject: false,
        }),
        err => ({
            value: err,
            isResolve: false,
            isReject: true,
        })
    )
}

/**
 * asyncTo的isResolve语法糖
 * @param { any | Promise<any> } pm 目标Promise或值
 * @returns { Promise<boolean> } 是否为resolve的Promise
 */
asyncTo.isResolve = pm => asyncTo(pm).then(({ isResolve }) => isResolve)

/**
 * asyncTo的isReject语法糖
 * @param { any | Promise<any> } pm 目标Promise或值
 * @returns { Promise<boolean> } 是否为reject的Promise
 */
asyncTo.isReject = pm => asyncTo(pm).then(({ isReject }) => isReject)

/**
 * 校验是否可用值（表单填写）
 * @param { any | number | string } val 需要校验的值
 * @returns { Boolean } 校验结果
 */
export const isAvailable = val => val !== '' && val !== null && val !== undefined

/**
 * 校验是否为数字
 * @param { any | number | string } val 需要校验的值
 * @returns { Boolean } 校验结果
 */
export const isNumeric = val => !isNaN(parseFloat(val)) && isFinite(val)

/**
 * 格式化金额
 * @param { any | number | string } num 需要格式化的金额
 * @returns { '' | `¥${number}` } 格式化后的金额
 */
export const formatMoney = num => {
    if (!isNumeric(num)) return ''
    return Number(num).toLocaleString('zh', {
        style: 'currency',
        currency: 'CNY'
    })
}

/**
 * 格式化数字
 * @param { any | number | string } num 需要格式化的数字
 * @returns { string } 格式化后的数字
 */
export const formatNumber = (num) => {
    if (!isNumeric(num)) return ''
    return Number(num).toLocaleString('zh', {
        style: 'decimal'
    })
}

/**
 * 防抖
 * @example const cb = debounce(fn, 300);<input @input="cb"/>
 * @param { function } fn 超时后回调函数
 * @param { Number } [ t = 300 ] 超时时长
 * @returns { function } 执行函数
 */
export const debounce = (fn, t = 300) => {
    let tid = null
    let time = 0
    return function(...args) {
        const now = Date.now()
        if (now - time > t) {
            fn?.apply(this, args)
        } else {
            clearTimeout(tid)
            tid = setTimeout(() => fn?.apply(this, args), t)
        }
        time = now
    }
}

/**
 * 节流
 * @example const cb = throttled(fn, 30);<input @input="cb"/>
 * @param { function } fn 超时后回调函数
 * @param { Number } [ t = 30 ] 超时时长
 * @returns { function } 执行函数
 */
export const throttled = (fn, t = 30) => {
    let isRun = false
    return function(...args) {
        if (isRun) return
        isRun = true
        fn?.apply(this, args)
        setTimeout(() => isRun = false, t)
    }
}

/**
 * 注入监听函数执行（无视项目是否启用了摇树优化）
 * @param { object } object.target 要监听的函数所在对象
 * @param { string } object.prop 要监听的函数名称
 * @param { function } [ object.targetProp ] 要监听的函数（会被摇树优化时必传）
 * @param { function } [ object.onBefore ] 监听函数执行前的回调函数（可阻止执行、修改执行传参）
 * @param { function } [ object.onceBefore ] 只执行一次的onBefore
 * @param { function } [ object.onAfter ] 监听函数执行后的回调函数（可修改执行的返回值）
 * @param { function } [ object.onceAfter ] 只执行一次的onAfter
 * @param { boolean } [ object.airNew = true ] 是否在要监听的函数不存在时新建一个
 * @returns { function } 传入要取消本次注册监听的函数名称
 */
export const injection = (() => {
    const cache = Symbol()
    return ({
        target,
        prop,
        targetProp,
        onBefore,
        onceBefore,
        onAfter,
        onceAfter,
        airNew = true,
    }) => {
        // 函数不存在则新建一个
        if (airNew && !target[prop] && !targetProp) target[prop] = targetProp = function() {}
        // 启用摇树优化时target[prop]为空
        const targetFn = target[prop] || targetProp
        const newOps = {
            onBefore,
            onceBefore,
            onAfter,
            onceAfter,
        }
        // 取消指定的监听
        const cancel = fnName => delete newOps[fnName]
        // 是否监听过
        if (targetFn[cache]) {
            targetFn[cache].push(newOps)
            return cancel
        }
        // 启用摇树优化时必须复制函数
        const oldFn = target[prop] || targetProp.bind({})
        // 创建监听
        const newFn = function(...args) {
            // 执行前
            for (const ops of newFn[cache]) {
                if (ops.onBefore?.call(this, args, cache) === cache) return
                if (ops.onceBefore) {
                    const rid = ops.onceBefore.call(this, args, cache)
                    // 清除只执行一次的项
                    delete ops.onceBefore
                    if (rid === cache) return
                }
            }
            // 执行中
            const oldRs = [oldFn.apply(this, args)]
            // 执行后
            for (const ops of newFn[cache]) {
                ops.onAfter?.call(this, args, oldRs)
                if (ops.onceAfter) {
                    ops.onceAfter.call(this, args, oldRs)
                    // 清除只执行一次的项
                    delete ops.onceAfter
                }
            }
            return oldRs[0]
        }
        newFn[cache] = []
        newFn[cache].push(newOps)
        Object.defineProperty(target, prop, {
            writable: true,
            value: newFn,
        })
        return cancel
    }
})()

// 删除指定缓存页面的缓存（匹配完整页面地址：/a/b/c?id=1）
export const removeKeepAliveCacheForName = fullPath => {
    if (typeof fullPath !== 'string') {
        throw new Error('removeKeepAliveCacheForName 参数必须为字符串！')
    }
    fullPath = encodeURI(fullPath.trim())
    delete $store.state.tagsView.cacheInstance[fullPath]
}

// 清除指定页面缓存并跳转（匹配完整页面地址：/a/b/c?id=1）
export const clearPageCacheTo = async (to, close, options) => {
    options = {
        // 是否重新打开跳转页
        reopen: true,
        ...options
    }
    if (typeof to !== 'string') {
        throw new Error('clearPageCacheTo 参数必须为字符串！')
    }
    to = encodeURI(to.trim())
    const from = router.currentRoute.fullPath
    // 记录跳转页面的来源
    close || $store.dispatch('tagsView/addBackViews', to)
    // 清除跳转页面缓存和标签
    if(options.reopen){
        delete $store.state.tagsView.cacheInstance[to]
        $store.dispatch('tagsView/delVisitedView_only', to)
        $store.dispatch('tagsView/delCachedView', to)
    }
    // 跳转
    await router.push(to)
    // 删除旧页面标签
    // return
    if (systemSettings.openNewPage && !close) return
    $store.dispatch('tagsView/delVisitedView_only', { fullPath: from })
}

// 全局路由更新前守卫
export const routeEach = {
    // 当前页面
    newThis: null,
    // 事件列表
    listeners: [],
    // 获取当前页面实例
    getThis(_this) {
        return _this.$route?.matched.at(-1)?.instances.default === _this ? _this : null
    },
    // 执行事件
    run(_this) {
        _this = this.getThis(_this)
        if (!_this || _this === this.newThis) return
        this.newThis = _this
        for (let i = this.listeners.length - 1; i > -1; i--) {
            const [cb, once] = this.listeners[i]
            cb.call(_this)
            once && this.listeners.splice(i, 1)
        }
    },
    // 添加事件
    on(cb) {
        this.listeners.push([cb])
    },
    // 添加事件-只执行一次
    once(cb) {
        this.listeners.push([cb, true])
    },
    // 移除事件
    off(cb) {
        this.listeners = this.listeners.filter(e => e[0] !== cb)
    },
}

/**
 * 通信对象构造器
 * @param {boolean} [emitCache = true] 是否缓存触发器
 * @param {boolean} [emitOnce = false] 同名触发器是否只存在一个
 * @param {boolean} [onOnce = false] 同名监听器是否只存在一个
 * @returns 通信对象
 */
export class EventChannel {
    static id = 0
    /**
     * @type { {[key: keyof any]: Array<(...args: any[]) => void>} }
     */
    listeners = {}
    /**
     * @type { {[key: keyof any]: any[][]} }
     */
    emitCaches = {}
    id
    emitCache
    emitOnce
    onOnce
    constructor({
        emitCache = true,
        emitOnce = false,
        onOnce = false,
    } = {}) {
        this.id = ++EventChannel.id
        this.emitCache = emitCache
        this.emitOnce = emitOnce
        this.onOnce = onOnce
    }
    /**
     * @param {keyof any} eventName 
     * @param {any[]} args 
     * @returns {(() => void) | undefined}
     */
    emit(eventName, ...args) {
        const listener = this.listeners[eventName]
        // 已存在监听器，直接触发
        if (listener) {
            listener.forEach(cb => cb(...args))
            return
        }
        // 不存在监听器，缓存
        if (!this.emitCache) return
        const emitCache = this.emitCaches[eventName] ||= []
        if (this.emitOnce) {
            emitCache[0] = args
        } else {
            emitCache.push(args)
        }
        // 返回取消缓存函数
        return () => {
            const index = emitCache.indexOf(args)
            index > -1 && emitCache.splice(index, 1)
            if (emitCache.length) return
            delete this.emitCaches[eventName]
        }
    }
    /**
     * @param {keyof any} eventName 
     * @param {(...args: any[]) => void} cb 
     * @returns {() => void}
     */
    on(eventName, cb) {
        const listener = this.listeners[eventName] ||= []
        if (this.onOnce) {
            listener[0] = cb
        } else {
            listener.push(cb)
        }
        // 存在触发器缓存，触发
        if (this.emitCache) {
            const emitCache = this.emitCaches[eventName]
            if (emitCache) {
                emitCache.forEach(args => cb(...args))
                delete this.emitCaches[eventName]
            }
        }
        // 返回取消监听函数
        return () => this.off(eventName, cb)
    }
    /**
     * @param {keyof any} eventName 
     * @param {(...args: any[]) => void} cb 
     * @returns {() => void}
     */
    once(eventName, cb) {
        const cancel = () => this.off(eventName, _cb)
        const _cb = (...args) => {
            cancel()
            cb(...args)
        }
        this.on(eventName, _cb)
        // 返回取消监听函数
        return cancel
    }
    /**
     * @param {keyof any} eventName 
     * @param {(...args: any[]) => void} cb 
     * @returns {void}
     */
    off(eventName, cb) {
        const listener = this.listeners[eventName]
        if (!listener) return
        if (cb) {
            // 删除指定监听器
            const index = listener.indexOf(cb)
            index > -1 && listener.splice(index, 1)
            if (listener.length) return
            delete this.listeners[eventName]
        } else {
            // 删除所有监听器
            delete this.listeners[eventName]
        }
    }
    /**
     * @param {keyof any} eventName 
     * @returns { {pm: Promise<any>, cancel: (() => void) | undefined} }
     */
    emit_pm(eventName) {
        let cancel
        const pm = new Promise((resolve) => {
            cancel = this.emit(eventName, resolve)
        })
        return { pm, cancel }
    }
    /**
     * @param {keyof any} eventName 
     * @returns {any}
     */
    emit_sync(eventName) {
        let run
        this.emit(eventName, (data) => run = data)?.()
        return run
    }
}

/**
 * 通信对象类型
 * @typedef {InstanceType<typeof EventChannel>} EventChannelType
 */

/**
 * 跟随组件自动卸载通讯对象中的监听器/触发器
 * @param {EventChannelType} eventChannel 目标通信对象
 * @example
 *  const event = autoOffEventChannel.call(this, new EventChannel())
 */
export function autoOffEventChannel(eventChannel) {
    /**
     * @type { Array<() => void> }
     */
    const cancels = []
    /**
     * @type { Pick<EventChannelType, 'on' | 'once' | 'emit' | 'emit_pm'> & { destroy: () => void } }
     */
    const map = {
        on: (eventName, cb) => {
            const cancel = eventChannel['on'](eventName, cb)
            cancels.push(cancel)
            return cancel
        },
        once: (eventName, cb) => {
            const cancel = eventChannel['once'](eventName, cb)
            cancels.push(cancel)
            return cancel
        },
        emit: (eventName, ...args) => {
            const cancel = eventChannel['emit'](eventName, ...args)
            cancel && cancels.push(cancel)
            return cancel
        },
        emit_pm: (eventName) => {
            const run = eventChannel['emit_pm'](eventName)
            run.cancel && cancels.push(run.cancel)
            return run
        },
        destroy: () => {
            cancels.forEach(cancel => cancel())
            cancels.length = 0
        }
    }
    if (this?.constructor?.name === 'VueComponent') {
        this.$once('hook:beforeDestroy', map.destroy)
    }
    return new Proxy(eventChannel, {
        /**
         * @param {keyof typeof map} propKey 
         */
        get(target, propKey) {
            return map[propKey] || Reflect.get(target, propKey)
        }
    })
}

/**
 * 获取下一个页面通信对象
 * @param { (event: EventChannelType) => void } cb 获取到下一个页面通信对象后的回调
 * @example
 * A页面
 *  function toB() {
 *      this.$router.push('/b')
 *      getOpenerEventChannel(event => {
 *          event.emit('test', 'hello')
 *      })
 *  }
 * B页面
 *  created() {
 *      this.$eventChannel?.once('test', data => {
 *          console.log(data) // hello
 *      })
 *  }
 */
export const getOpenerEventChannel = cb => routeEach.once(function() {
    cb(this.$eventChannel ??= new EventChannel())
})

/**
 * 把传入的页面通信对象上的listeners和emitCaches，合并到下一个页面通信对象上
 * @param {EventChannelType} [ eventChannel ] 要合并的通信对象，通常是当前页面的通信对象
 * @example
 *  function toB() {
 *      this.$router.replace('/b')
 *      mergeNewPageEventChannel(this.$eventChannel)
 *  }
 */
export function mergeNewPageEventChannel(eventChannel) {
    if (!eventChannel) return
    getOpenerEventChannel(newEventChannel => {
        Object.assign(newEventChannel.listeners, eventChannel.listeners)
        Object.assign(newEventChannel.emitCaches, eventChannel.emitCaches)
    })
}

// 表单校验
export const formRules = (() => {
    const rules = {
        // 其他
        ...Object.entries(regExp).reduce((obj, [key, value]) => {
            obj[key] = { pattern: value, message: '格式不正确', trigger: 'blur' }
            return obj
        }, {}),
        // 是否为空
        $noAir: { required: true, pattern: /./, message: '不能为空', trigger: 'change' },
    }
    return (...args) => args.map(key => rules[key])
})()
// 表单校验-字符串长度
formRules.stringLength = (minLength, maxLength = minLength) => ({
    min: minLength,
    max: maxLength,
    message: `长度应为 ${minLength === maxLength ? maxLength : `${minLength} 到 ${maxLength}`} 个字符`,
})
// 表单校验-数组最小长度
formRules.arrayMinLength = (minLength = 1, errorMessage = '至少要有 {minLength} 条') => [{
    required: true,
    type: 'array',
    min: minLength,
    message: errorMessage.replace('{minLength}', minLength),
}]

// 格式化字典数据，传入obj或json（对象key不要为数字）
export class Dicts {
    $obj = null
    $keyList = []
    $valList = []
    $json = []
    #json_key = null
    get $json_key() {
        return this.#json_key
    }
    // 更新$json_key时，更新$json
    set $json_key(arr) {
        this.#json_key = arr
        this.#initJson()
    }
    // 创建字典
    constructor(data, key = 'label', val = 'value') {
        this.$obj = Array.isArray(data) ? data.reduce((obj, item) => {
            obj[item[key]] = item[val]
            return obj
        }, {}) : data
        this.$keyList = Object.keys(this.$obj)
        this.$valList = Object.values(this.$obj)
        this.$keyList.forEach((key, index) => {
            this[key] = this.$valList[index]
            this[this.$valList[index]] = key
        })
        this.$json_key = [key, val]
    }
    // 创建json
    #initJson() {
        this.$json = this.$keyList.map((key, index) => ({
            [this.$json_key[0]]: key,
            [this.$json_key[1]]: this.$valList[index],
        }))
    }
    // 过滤字典
    filterFn(...list) {
        return new Dicts(this.$json.filter(item => list.includes(item[this.$json_key[0]])), ...this.$json_key)
    }
    // 删除字典
    deleteFn(...list) {
        return new Dicts(this.$json.filter(item => !list.includes(item[this.$json_key[0]])), ...this.$json_key)
    }
    // 扩展字典
    extendFn(obj) {
        return new Dicts(Object.assign({}, this.$obj, obj), ...this.$json_key)
    }
}

// 检测重名
export const checkName = (errTips = '存在重名！') => {
    const names = new Set()
    return Object.assign(
        (...arr) => arr.forEach(name => {
            if (names.has(name)) {
                console.error(errTips)
                debugger
            } else {
                names.add(name)
            }
        }),
        { names }
    )
}

// 挂载为指令式组件-默认配置
const mountDirectiveComponent_defaultHook = ctx => {
    ctx.$once('hook:mounted', () => {
        const rootCtx = ctx.$children[0]
        const rootName = rootCtx?.$options.name
        // 当根组件为ElDialog时，监听关闭事件
        if (rootName === 'ElDialog') {
            rootCtx.$once('closed', () => ctx.$destroy())
        }
    })
}

// 挂载为指令式组件
export const mountDirectiveComponent = ({ default: def, ...others }) => Object.assign(
    data => {
        const Constructor = Vue.extend(def)
        const ctx = new Constructor({ data })
        mountDirectiveComponent_defaultHook(ctx)
        ctx.$once('hook:beforeDestroy', () => ctx.$el.remove())
        ctx.$mount()
        document.body.appendChild(ctx.$el)
        return ctx
    },
    others
)

// 可初始化的响应式对象Vue.observable
export const onceObservable = (fn, _this = Vue.prototype) => {
    if (typeof fn !== 'function') return new Error('onceObservable 参数必须为函数！')
    const getData = () => Vue.observable(fn.prototype ? fn.call(_this, _this) : fn(_this))
    const data = getData()
    if (typeof data !== 'object') return new Error('onceObservable 传参函数返回值必须为对象！')
    const init = () => {
        const newData = getData()
        const total = new Set([
            ...Object.keys(data),
            ...Object.keys(newData),
            ...Object.getOwnPropertySymbols(data),
            ...Object.getOwnPropertySymbols(newData),
        ])
        total.forEach(key => {
            Object.hasOwn(newData, key) ? _this.$set(data, key, newData[key]) : _this.$delete(data, key)
        })
        return data
    }
    return { init, data }
}

/**
 * el-table合计函数
 * @param { object } params 参数
 * @param { { string: number | function } } params.labels 合计列的标题和对应的保留小数位数/格式化函数
 * @param { function } [ params.formatter = elTableSum.defaultFormatter ] 默认格式化函数
 * @param { string } [ params.title = '总计' ] 总计标题
 * @param { string } [ params.other = '' ] 其他列的值
 * @returns { function } 合计函数
 * @example
 * <el-table
 *      :data="data"
 *      show-summary
 *      :summary-method="$tools.elTableSum({
 *          labels: {
 *              '数量': 0,
 *              '金额': 2,
 *          }
 *      })"
 * >
 */
export const elTableSum = (params) => ({ columns, data }) => {
    const {
        labels = {},
        formatter = elTableSum.defaultFormatter,
        title = '总计',
        other = '',
    } = params
    const labelsArr = Object.keys(labels)
    const sums = []
    columns.forEach((column, index) => {
        if (index === 0) {
            sums[index] = title
            return
        }
        sums[index] = other
        if (labelsArr.includes(column.label)) {
            const value = labels[column.label]
            const valueType = typeof value
            switch (valueType) {
                case 'function':
                    sums[index] = value(data, column)
                    break
                case 'number':
                    sums[index] = formatter(data, column, value)
                    break
                default:
                    // 其他类型返回原值
                    sums[index] = value
            }
        }
    })
    return sums
}
/**
 * el-table合计函数-默认格式化
 * @param { object } column 表格列
 * @param { object[] } data 表格数据
 * @param { number } float 保留小数位数
 * @returns { string } 合计值
 */
elTableSum.defaultFormatter = (data, column, float = 0) => {
    if (column.formatter) {
        return data.reduce(
            (pre, row) => pre.plus(column.formatter(row)),
            Big(0),
        ).toFixed(float)
    }
    const nums = data.map(item => Number(item[column.property]) || 0)
    const sum = nums.reduce((total, num) => Big(total).plus(num), 0)
    return sum.toFixed(float)
}

/**
 * 防连点函数
 * @param { number } [ time = 3000 ] 超时时长
 * @returns { boolean } 是否为连点
 * @example
 *  handleExport() {
 *      if (
 *          this.$tools.debounceClick.call(this.handleExport)
 *      ) return
 *     // ...
 *  }
 */
export function debounceClick(time = 3000) {
    if (this.debounceClick === debounceClick) {
        throw new Error('防连点函数必须挂载到执行函数上！')
    }
    if (this[debounceClick.sy]) return true
    this[debounceClick.sy] = true
    setTimeout(
        () => this[debounceClick.sy] = false,
        time
    )
    return false
}
debounceClick.sy = Symbol()

/**
 * 获取屏幕DPI
 * @returns { number } 屏幕DPI
 */
export const getDPI = () => {
    const dom = document.createElement('div')
    dom.style.cssText = `
        width: 1in;
        position: fixed;
        top: -9in;
        visibility: hidden;
    `
    document.documentElement.appendChild(dom)
    const dpi = dom.offsetWidth
    dom.parentNode.removeChild(dom)
    return dpi
}

/**
 * pt转px
 * @param { number } pt pt值
 * @returns { number } px值
 */
export const ptToPx = pt => pt * getDPI() / 72

/**
 * 注册与iframe通信-页面加载完成
 * @param { function } cb 回调函数
 */
export const registerIframe_pageReady = (cb) => {
    window.addEventListener('message', (win) => {
        if (win.data?.type !== 'page:ready') return
        cb(win.data)
    }, { once: true })
}

/**
 * 执行与iframe通信-截图生成下载pdf
 * @param { Object } param0 参数
 * @param { HTMLIFrameElement } param0.iframeRef iframe元素
 * @param { String } param0.fileName 下载文件名
 * @param { String } [ param0.format = 'a4' ] 纸张格式
 * @param { Object } [ param0.canvasOptions ] 截图参数（接收端）
 * @returns { Promise<function> } 下载函数
 */
export const executionIframe_PrtSc = ({
    iframeRef,
    fileName,
    format = 'a4',
    canvasOptions = {},
}) => new Promise((resolve, reject) => {
    const pt = executionIframe_PrtSc._pts[format]
    // 通知iframe执行截图
    iframeRef.contentWindow.postMessage({
        type: 'PrtSc:execution',
        data: {
            ...canvasOptions,
            width: ptToPx(pt.w),
        }
    }, '*')
    // 监听截图结果
    window.addEventListener('message', (win) => {
        if (win.data?.type === 'PrtSc:reject') {
            Message.error('下载失败')
            reject()
            return
        }
        if (win.data?.type !== 'PrtSc:resolve') {
            reject()
            return
        }
        const { base64, WHratio } = win.data.data
        const pdf = new JsPDF('portrait', 'pt', format)
        const height = pt.w / WHratio
        let top = 0
        while (true) {
            pdf.addImage(
                base64,
                'JPEG',
                0,
                top,
                pt.w,
                height,
            )
            top -= pt.h
            if (top + height > 0) {
                pdf.addPage()
            } else {
                break
            }
        }
        pdf.save(`${fileName}.pdf`)
        resolve()
    }, { once: true })
})
/**
 * 纸张尺寸列表
 */
executionIframe_PrtSc._pts = {
    a4: { w: 595.28, h: 841.89 },
}
export const formateDicts = {
    flat: (dicts) => {
        const res = {
            list:[],
        }
        Object.entries(dicts).forEach(( [key ,value] )=>{
            const _key = isNaN(+key) ? key : +key
            res.list.push({ label:value, value: _key})
            res[key] = value
            res[value] = _key
        })
        return res
    },
    complicated: (
        dicts,
        opt = {
            label: 'label',
            value: 'id'
        }
    ) => {
        const { value, label } = opt
        const res = {}
        dicts.forEach(item=> {
            if(item[value] === undefined) return
            res[item[label]] = item[value]
            res[item[value]] = item[label]
        })
        return res
    }
}
